package Paquete_agenetico;

//import Interfaz;
import Paquete_auxiliar.MyRandom;
import Paquete_cromosoma.Cromosoma;
import Paquete_cromosoma.Cromosoma_f1;
import Paquete_cromosoma.Cromosoma_f2;
import Paquete_cromosoma.Cromosoma_f3;
import Paquete_cromosoma.Cromosoma_f4;
import Paquete_gen.Gen;

import java.util.ArrayList;
import java.util.Iterator;

public class AGenetico {
   
    protected ArrayList<Cromosoma> pob; //poblacion
    protected ArrayList<Cromosoma> crom_seleccionados; //Cromosoma devueltos por el metodo de seleccion 
    protected int tam_pob; //tamano poblacion
    protected int num_max_gen; //numero maximo de generaciones
    protected Cromosoma elMejor; //mejor individuo
    protected Cromosoma mejorDeLaGeneracion; //el mejor de la generacion: pob[pos_mejor]
    protected int pos_mejor; //posicion del mejor cromosoma
    protected double prob_cruce; //probabilidad de cruce
    protected double prob_mut; //probabilidad de mutacion 
    protected double tol; //tolerancia de la representacion

    public AGenetico(int tam_pob, int num_max_gen, double prob_cruce, double prob_mut, double tol) {
        this.pob = new ArrayList<Cromosoma>();
        this.tam_pob = tam_pob;
        this.num_max_gen = num_max_gen;
        this.prob_cruce = prob_cruce;
        this.prob_mut = prob_mut;
        this.tol = tol;
    }
    
    
    private Cromosoma crear_crom(int num_fun){ //crear cromosoma segun funcion
    	Cromosoma c = null;
    	switch(num_fun) { // Elige la opcion acorde al numero de la funcion
			case 1: c = (Cromosoma) new Cromosoma_f1(tol); break;
			case 2: c = (Cromosoma) new Cromosoma_f2(tol); break;
			case 3: c = (Cromosoma) new Cromosoma_f3(tol); break;
			case 4: c = (Cromosoma) new Cromosoma_f4(tol); break;
			//case 5: c = (Cromosoma) new Cromosoma_f5(tol); break;
    	}
    	return c;    	
    }
    
    
    private void inicializa(int num_fun){
    	elMejor = crear_crom(num_fun);    	
    	elMejor.setAptitud(-Double.MAX_VALUE);
    	
    	System.out.println("----------Funcion inicializa(crea los individuos)---------------");
    	pob = new ArrayList<Cromosoma>();
        for(int j=0; j<this.tam_pob; j++){
        	Cromosoma c = crear_crom(num_fun);
            pob.add(c);
            pob.get(j).inicializaCromosoma();
            pob.get(j).setAptitud(pob.get(j).evalua());
            System.out.println(pob.get(j).getGenes().get(0).getArray_genes());
            
        }
    }
    
    private void evaluarPoblacion(){
        double punt_acum = 0; //puntuacion acumulada
        double aptitud_mejor = 0; //mejor aptitud
        double sumaptitud = 0; //suma de la aptitud
        
        for(int i=0; i<this.tam_pob; i++){
            sumaptitud = sumaptitud + pob.get(i).getAptitud();
            if(pob.get(i).getAptitud() > aptitud_mejor){
                pos_mejor = i;
                aptitud_mejor = pob.get(i).getAptitud();
            }
        }//Aqui ya tenemos calculada la Aptitud total: sumaptitud para calcular puntuacion y punt_acum
        
        for (int j=0; j<this.tam_pob; j++){
        	pob.get(j).setPuntuacion(pob.get(j).getAptitud() / sumaptitud);
        	pob.get(j).setPunt_acum(pob.get(j).getPuntuacion() + punt_acum);
        	punt_acum += pob.get(j).getPuntuacion();
        }
        
        this.mejorDeLaGeneracion = pob.get(pos_mejor);
    }
    
    /*****************************************SELECCION**************************************************************/
    public void seleccion(String tipo_sel){
    	//ArrayList<Cromosoma> nueva_pob = new ArrayList<Cromosoma>();
    	int z=2;//Para seleccion por torneo(generalmente se escogen 2 individuos). 2 o 3 es lo normal
    	
    	System.out.println();
    	if (tipo_sel.equals("ruleta")) seleccion_ruleta();
    	else if (tipo_sel.equals("torneo")) seleccion_torneo(z);
    	//else if (tipo_sel.equals("estocastico")) seleccion_estocastico();
    	//else if (tipo_sel.equals("restos")) seleccion_restos(this.pob, nueva_pob, this.tam_pob);
    	
    	
    }
    
    
	private void seleccion_ruleta() {
		System.out.println("-----------SELECCIONADOS POR RULETA: -----------------");
    	int[] sel_super;
    	sel_super = new int[tam_pob]; //seleccionados para sobrevivir
    	double prob; //probabilidad de la seleccion
    	int pos_super; //posicion del superviviente
    	
    	
    	for (int i=0; i<tam_pob; i++){
    		prob = Math.random();
    		pos_super = 0;
    		while ((prob > pob.get(pos_super).getPunt_acum()) && (pos_super < tam_pob)){
    			pos_super++;
    		}
    		sel_super[i] = pos_super;
    	}
    	
    	crom_seleccionados = new ArrayList<Cromosoma>();  
    	//generamos la poblacion intermedia
    	for (int j=0; j<tam_pob; j++){
    		crom_seleccionados.add(pob.get(sel_super[j]).copia());
    		System.out.println("Individuo "+ j + ": " + this.crom_seleccionados.get(j).getGenes().get(0).getArray_genes());
    	}
    	
    	
	}
    
    private void seleccion_torneo(int z) {
    	System.out.println("-----------SELECCIONADOS POR TORNEO: -----------------");
    	//Es seleccion mediante torneo probablistico
    	int[] sel_super;
    	sel_super = new int[tam_pob]; //seleccionados para sobrevivir
    	
    	//Se van a seleccionar aquellos individuos con mayor aptitud, tras luchar entre ellos
    	for (int i=0; i<tam_pob; i++){
    		int gana = 0;
    		double punt_gana = -Double.MAX_VALUE; //lo inicializamos a -infinito
    		for (int j=0; j<z; j++){
    			double p = Math.random();
    			int individuo = (int) Math.floor(p*tam_pob);
    			if (pob.get(individuo).getAptitud() > punt_gana){
    				gana = individuo;
    				punt_gana = pob.get(individuo).getAptitud();
    			}
    		}
    		sel_super[i] = gana;
    	}    	
    	
    	crom_seleccionados = new ArrayList<Cromosoma>(); 
    	//generamos la poblacion intermedia
    	for (int j=0; j<tam_pob; j++){
    		crom_seleccionados.add(pob.get(sel_super[j]).copia());
    		System.out.println("Individuo "+ j + ": " + this.crom_seleccionados.get(j).getGenes().get(0).getArray_genes());
    	}	
	}
    

    /*TERMINAR SELECCION POR RESTOS
     * 
     * private void seleccion_restos(ArrayList<Cromosoma> pob2, ArrayList<Cromosoma> nueva_pob, int tam_pob2) {
		//Se colocan tantas marcas espaciadas por igual como individuos queremos seleccionar (N)
    	// La distancia entre las marcas es 1/N
    	int[] sel_super;
    	sel_super = new int[tam_pob2]; //seleccionados para sobrevivir
    	double dist = 1/tam_pob2;   	
		
	}*/

    /*****************************************CRUCE**************************************************************/
    private void fase1(Cromosoma padre1, Cromosoma padre2, 
    				   ArrayList<Boolean> padre1_aux, ArrayList<Boolean> padre2_aux){
    	Gen x,y;    	
    	for (int i=0; i < padre1.getNumGenes(); i++){
    		x = padre1.getGenes().get(i);
    		y = padre2.getGenes().get(i);
    		for (int j=0; j < x.getArray_genes().size(); j++){
    			padre1_aux.add(true); //PARCHE CUTRE
    			padre2_aux.add(true); //PARCHE CUTRE
    			padre1_aux.set(i+j, x.getArray_genes().get(j));
    			padre2_aux.set(i+j, y.getArray_genes().get(j));
    		}
    	}
    }
    
    private void fase2(ArrayList<Boolean> padre1_aux, ArrayList<Boolean>padre2_aux,int puntocruce,
    				   ArrayList<Boolean>hijo1_aux, ArrayList<Boolean> hijo2_aux){    	
    	int i;    	
    	for (i=0; i < puntocruce; i++){
    		boolean auxG = padre1_aux.get(i);
    		hijo1_aux.add(true); //PARCHE CUTRE
    		hijo1_aux.set(i,auxG); //insertar la parte del padre1 en el hijo1  
    		
    		boolean auxG2 = padre2_aux.get(i);
    		hijo2_aux.add(true); //PARCHE CUTRE
    		hijo2_aux.set(i,auxG2); //insertar la parte del padre2 en el hijo2    		
    	}
    	
    	for (i=puntocruce; i < padre1_aux.size(); i++ ){
    		boolean auxG = padre2_aux.get(i);    
    		hijo1_aux.add(true); //PARCHE CUTRE
    		hijo1_aux.set(i,auxG); //insertar la parte del padre2 en el hijo1    
    		
    		boolean auxG2 = padre1_aux.get(i);
    		hijo2_aux.add(true); //PARCHE CUTRE
    		hijo2_aux.set(i,auxG2); //insertar la parte del padre1 en el hijo2
    	}
    }
    
    private void fase3(ArrayList<Boolean> hijo1_aux, ArrayList<Boolean>hijo2_aux,
    				   Cromosoma hijo1, Cromosoma hijo2){
    	
    	for (int i=0; i < hijo1.getNumGenes(); i++){
    		for (int j=0; j < hijo1.getGenes().get(0).getArray_genes().size(); j++){
    			hijo1.getGenes().get(i).getArray_genes().set(j, hijo1_aux.get(i+j));    			
    			hijo2.getGenes().get(i).getArray_genes().set(j, hijo2_aux.get(i+j));
    		}
    	}
    	
    	System.out.println(hijo1.getGenes().get(0).getArray_genes());
    	System.out.println(hijo2.getGenes().get(0).getArray_genes());
    }
    
    /*public void cruce(Cromosoma padre1, Cromosoma padre2, Cromosoma hijo1, Cromosoma hijo2, int puntocruce){
    	
    	int num_fun=0;
    	if (padre1 instanceof Cromosoma_f1) num_fun=1;
    	else if (padre1 instanceof Cromosoma_f2) num_fun=2;
    	else if (padre1 instanceof Cromosoma_f3) num_fun=3;
    	else if (padre1 instanceof Cromosoma_f4) num_fun=4;
    	//else if (padre1 instanceof Cromosoma_f5) num_fun=5;
    	
    	hijo1 = crear_crom(num_fun);
    	hijo2 = crear_crom(num_fun);    	
    	hijo1.inicializaCromosoma();
    	hijo2.inicializaCromosoma();   	
    	
    	//Llamamos a las fases correspondientes
    	ArrayList<Boolean> padre1_aux = new ArrayList<Boolean>(); 
    	ArrayList<Boolean> padre2_aux = new ArrayList<Boolean>(); 
    	ArrayList<Boolean> hijo1_aux = new ArrayList<Boolean>(); 
    	ArrayList<Boolean> hijo2_aux = new ArrayList<Boolean>(); 
    	fase1(padre1, padre2, padre1_aux, padre2_aux); //Pasar cada padre a un array de boolean(no entendemos de genes)
    	fase2(padre1_aux, padre2_aux, puntocruce, hijo1_aux, hijo2_aux); //cruzarse
    	fase3(hijo1_aux, hijo2_aux, hijo1, hijo2); //restaurar los padres, generando los genes again
    	
    	//ahora evaluamos los nuevos hijos
    	hijo1.setAptitud(hijo1.evalua()); 	
    	hijo2.setAptitud(hijo2.evalua());   	
    	
    }*/
    
    public void reproducion(){
    	int cont_cruce = this.tam_pob;
    	ArrayList<Cromosoma> pob_aux = new ArrayList<Cromosoma>();
    	double p;
    	Cromosoma padre1=null, padre2=null, hijo1=null, hijo2=null;
    	
    	if(cont_cruce % 2 == 1){
    		pob_aux.add(this.crom_seleccionados.get(this.tam_pob-1).copia());
    		cont_cruce--;
    	}
    	
    	int i=0;
    	while(i < cont_cruce){
    		//En primer lugar, generamos un numero aleatorio(p). si p es menor que prob_cruce existe cruce. Si no, no hacer nada
        	//En caso de que haya reproduccion, generamos dos padres, que daran lugar a 2 cadenas hijas
        	p = Math.random();
        	if (p < this.prob_cruce){ //hay emparejamiento        		
        		int puntoCorte = MyRandom.intRandom((pob.get(0).getLongitudCromosoma())-1); //Calcular puntoCorte
        		System.out.println();
        		System.out.println("--------------Hay CRUCE en punto: " + puntoCorte + " ---------------------");
        		padre1 = crom_seleccionados.get(i).copia();
        		padre2 = crom_seleccionados.get(i+1).copia();
        		
        		//REALIZAMOS EL CRUCE       		
        		int num_fun=0;
            	if (padre1 instanceof Cromosoma_f1) num_fun=1;
            	else if (padre1 instanceof Cromosoma_f2) num_fun=2;
            	else if (padre1 instanceof Cromosoma_f3) num_fun=3;
            	else if (padre1 instanceof Cromosoma_f4) num_fun=4;
            	//else if (padre1 instanceof Cromosoma_f5) num_fun=5;
            	
            	hijo1 = crear_crom(num_fun);
            	hijo2 = crear_crom(num_fun);    	
            	hijo1.inicializaCromosoma();
            	hijo2.inicializaCromosoma();   	
            	
            	//Llamamos a las fases correspondientes
            	ArrayList<Boolean> padre1_aux = new ArrayList<Boolean>(); 
            	ArrayList<Boolean> padre2_aux = new ArrayList<Boolean>(); 
            	ArrayList<Boolean> hijo1_aux = new ArrayList<Boolean>(); 
            	ArrayList<Boolean> hijo2_aux = new ArrayList<Boolean>(); 
            	fase1(padre1, padre2, padre1_aux, padre2_aux); //Pasar cada padre a un array de boolean(no entendemos de genes)
            	fase2(padre1_aux, padre2_aux, puntoCorte, hijo1_aux, hijo2_aux); //cruzarse
            	fase3(hijo1_aux, hijo2_aux, hijo1, hijo2); //restaurar los padres, generando los genes again
            	
            	//ahora evaluamos los nuevos hijos
            	hijo1.setAptitud(hijo1.evalua()); 	
            	hijo2.setAptitud(hijo2.evalua());
        		
        		
        	} 
        	else { //no hay emparejamiento
        		System.out.println("-------------NO Hay CRUCE---------");
        		hijo1 = crom_seleccionados.get(i).copia();
        		hijo2 = crom_seleccionados.get(i+1).copia();
        	}
        	
        	pob_aux.add(hijo1);
        	pob_aux.add(hijo2);
    		i=i+2;
    	}
    	
    	pob = pob_aux;
    	
    }
    
    /*****************************************MUTACION**************************************************************/
    public void mutacion(){
    	
    	System.out.println();
    	System.out.println("---------------MUTACION----------------");
    	
    	int numGenes=0, tam_array=0;
    	double p=0;
    	boolean valor_bool=false;
    	boolean ha_mutado=false;
    	for (int individuo=0; individuo<this.tam_pob; individuo++){
    		numGenes = this.pob.get(individuo).getGenes().size(); //SALTA UNA EXCEPCION AQUI, NULLPOINTEREXCEPTION(Si no hay cruce, da fallo al mutar, porque no hay individuos)
    		for (int g=0; g<numGenes; g++){
    			tam_array = this.pob.get(individuo).getGenes().get(g).getArray_genes().size();
    			for (int pos_array=0; pos_array<tam_array; pos_array++){
    				
    				p = Math.random();
    				if (p<this.prob_mut){
    					System.out.println("MUTACION en individuo: " + individuo + ", en el gen " + g + ". En su posicion: " + pos_array );
    					valor_bool=this.pob.get(individuo).getGenes().get(g).getArray_genes().get(pos_array);
    					this.pob.get(individuo).getGenes().get(g).getArray_genes().set(pos_array, !valor_bool);  
    					ha_mutado=true;
    				}
    			}//for para cambiar el valor del array en caso de mutacion
    			
    			System.out.println(pob.get(individuo).getGenes().get(g).getArray_genes());
    		}//for para ver los genes  
    		
    		if(ha_mutado){//si ha mutado hay que recalcular su aptitud
    			this.pob.get(individuo).setAptitud(this.pob.get(individuo).evalua());
    		}
    		
    	}//for de los individuos
    		
    }
    
    
    /*****************************************Funciones Auxiliares***************************************************/ 
    private ArrayList<Cromosoma> copia (ArrayList<Cromosoma> nuevaPob) {
        ArrayList<Cromosoma> pobCopiada = new ArrayList<Cromosoma>();
        Iterator<Cromosoma> iter = nuevaPob.iterator();
        
        while(iter.hasNext()){
                Cromosoma c = iter.next();
                Cromosoma c_aux = c.copia();
                pobCopiada.add(c_aux);
        }
        return pobCopiada;
    }


	//getters and setters
    public ArrayList<Cromosoma> getPob() {
		return pob;
	}

	public void setPob(ArrayList<Cromosoma> pob) {
		this.pob = pob;
	}

	public int getTam_pob() {
		return tam_pob;
	}

	public void setTam_pob(int tam_pob) {
		this.tam_pob = tam_pob;
	}

	public int getNum_max_gen() {
		return num_max_gen;
	}

	public void setNum_max_gen(int num_max_gen) {
		this.num_max_gen = num_max_gen;
	}

	public Cromosoma getElMejor() {
		return elMejor;
	}

	public void setElMejor(Cromosoma elMejor) {
		this.elMejor = elMejor;
	}

	public int getPos_mejor() {
		return pos_mejor;
	}

	public void setPos_mejor(int pos_mejor) {
		this.pos_mejor = pos_mejor;
	}

	public double getProb_cruce() {
		return prob_cruce;
	}

	public void setProb_cruce(double prob_cruce) {
		this.prob_cruce = prob_cruce;
	}

	public double getProb_mut() {
		return prob_mut;
	}

	public void setProb_mut(double prob_mut) {
		this.prob_mut = prob_mut;
	}

	public double getTol() {
		return tol;
	}

	public void setTol(double tol) {
		this.tol = tol;
	}

	
	public static void main(String[] args) {
		int numgeneraciones = 1;
		AGenetico AG = new AGenetico(4,4,0.7,0.3,0.01); //crea poblacion inicial
		AG.inicializa(2); //la funcion 2
		AG.evaluarPoblacion(); //evalua los individuos y coge el mejor
		
		while(numgeneraciones < 3){
			System.out.println("*****************************************************************");
			System.out.println("**********************Generacion: " + numgeneraciones + "******************************");
			System.out.println("*****************************************************************");
			AG.seleccion("torneo");
			AG.reproducion();
			AG.mutacion();
			AG.evaluarPoblacion(); 
			numgeneraciones++;
		}
		
		System.out.println("Terminado y correcto");
	}
    
    
    
}
